package com.pfs.internal.timestamp.utils;

import java.security.cert.CertPathValidatorException.Reason;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.lang3.time.DateUtils;

import com.pfs.internal.timestamp.entities.CardHolder;
import com.pfs.internal.timestamp.entities.History;
import com.pfs.internal.timestamp.entities.Leave;
import com.pfs.internal.timestamp.entities.TimeStamp;
import com.pfs.internal.timestamp.entities.TrackDay;

public class TimeStampUtil {
	public static Map<String, List<TrackDay>> calculate(List<History> input, int year, int month, List<Leave> leaves, List<Date> outDates) {
		// Group by Account
		Map<String, List<History>> mapCard = groupHistoryByAccount(input);
		// Group on Account by Day
		Map<String, Map<Date, List<History>>> mapCardByDay = groupHistoryOnAccountByDate(mapCard, year, month);
		// Calculate on each Account
		Map<String, List<TrackDay>> mapTrack2 = calculateOnAllAccounts(mapCardByDay);
		// Loai bo ngay nghi
		Map<String, List<TrackDay>> mapTrack = removeOutDates(mapTrack2, outDates);
		// Get list leave from Excel
		Map<String, List<Leave>> mapLeave = null;
		// List<Leave> leaves = new ArrayList<Leave>();
		// Map session = ActionContext.getContext().getSession();
		//
		// leaves = (List<Leave>)session.get("UPLOAD_LEAVES");
		mapLeave = groupLeaveByAccount(leaves);

		// Calculate dtl result
		calculateDtl(mapTrack, mapLeave);
		// Calculate hdr result
		// List<TimeStamp> lstHdr = calculateHdr(mapDtl);
		// return mapTrack;
		System.out.println("mapDtl.size=" + mapTrack.size());
		return mapTrack;
	}

	@SuppressWarnings("deprecation")
	private static void calculateDtl(Map<String, List<TrackDay>> mapTrack, Map<String, List<Leave>> mapLeave) {
		// Map<String, List<TrackDay>> mapDtl = new TreeMap<String,
		// List<TrackDay>>();
		try {
			for (Map.Entry<String, List<TrackDay>> entryTrack : mapTrack.entrySet()) {
				// String key = entryTrack.getKey();
				// List<TrackDay> lstDtlByAccount = new ArrayList<TrackDay>();
				List<TrackDay> lstTrack = entryTrack.getValue();
				String username = lstTrack.get(0).getUsername().toUpperCase();

				for (TrackDay track : lstTrack) { // iterator each user by
													// trackday
					if (track.getLevel() == null) {
						track.setLevel(FoulLevel.DEFAULT);
						//System.out.println(track.getDate());
					}
					switch (track.getLevel()) {
					case CHECK_DOUBLE:
						track.setNote(TrackDay.NOTE_CHECK_DOUBLE);
						break;
					case NO_CHECKOUT:
						track.setNote(TrackDay.NOTE_NO_CHECKOUT);
						break;
					case LATE_NO_OUT:
						track.setNote(TrackDay.NOTE_LATE_NO_OUT);
						break;
					case CHECKIN_LATE:
						track.setNote(TrackDay.NOTE_CHECKIN_LATE);
						break;
					case CHECKOUT_SOON:
						track.setNote(TrackDay.NOTE_CHECKOUT_SOON);
						break;
					case HALF_DAY:
						track.setNote(TrackDay.NOTE_HALFDAY_LEAVE);
						break;
					case FULL_DAY:
						track.setNote(TrackDay.NOTE_FULLDAY);
						break;
					default:
						// track.setNote("DEFAULT");
						break;
					}
					if (track.isFail()) {
						Date date = track.getDate();
						FoulLevel level = track.getLevel();
						// boolean isFail = true;
						// ìf mapLeave.containKey(username) {get value by key}
						if (mapLeave.containsKey(username)) {
							List<Leave> lstLeave = mapLeave.get(username);
							for (Leave lv : lstLeave) {
								boolean halfday = lv.isHalfDay();
								boolean onday = lv.getFromDate().getDate() <= date.getDate() && date.getDate() <= lv.getToDate().getDate();
								if (onday && ((halfday && (level == FoulLevel.CHECK_DOUBLE || level == FoulLevel.HALF_DAY) || (!halfday)))) {
									// isFail = false;
									track.setFail(false);
									if (halfday) {
										track.setNote(TrackDay.NOTE_HALFDAY_LEAVE);
									} else {
										track.setNote(TrackDay.NOTE_FULLDAY_LEAVE);
									}
									break;
								}
							}
						}
					}
					// lstDtlByAccount.add(track);
				}
				/*
				 * if(lstDtlByAccount.size() > 0){
				 * mapDtl.put(entryTrack.getKey(), lstDtlByAccount); }
				 */
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public static List<TimeStamp> calculateHdr(Map<String, List<TrackDay>> mapDtl) {
		List<TimeStamp> lstHdr = new ArrayList<TimeStamp>();
		for (Map.Entry<String, List<TrackDay>> entry : mapDtl.entrySet()) {
			TimeStamp ts = new TimeStamp();
			ts.setAccount(entry.getKey());
			int foulCount = 0;
			StringBuilder note = new StringBuilder();
			for (TrackDay trackDay : entry.getValue()) {
				if (trackDay.isFail()) {
					switch (trackDay.getLevel()) {
					case CHECKIN_LATE:
						foulCount += 1;
						break;
					case CHECKOUT_SOON:
						foulCount += 1;
						break;
					case CHECK_DOUBLE:
						foulCount += 2;
						break;
					case NO_CHECKOUT:
						foulCount += 2;
						break;
					case FULL_DAY:
						foulCount += 1;
						// note.append("Ngày ").append(Formater.date2str(trackDay.getDate())).append(": ").append(TrackDay.NOTE_FULLDAY).append("\n");
						break;
					default:
						break;
					}

				}
			}
			if (foulCount > 4) {
				ts.setFoulCount(foulCount);
				ts.setFoul(true);
			} else {
				ts.setFoulCount(foulCount);
				ts.setFoul(false);
			}
			ts.setNote(note.toString());
			lstHdr.add(ts);
		}
		System.out.println("lstHdr.size=" + lstHdr.size());
		return lstHdr;
	}

	private static Map<String, List<TrackDay>> calculateOnAllAccounts(Map<String, Map<Date, List<History>>> mapCardByDay) {
		Map<String, List<TrackDay>> mapOnAcc = new TreeMap<String, List<TrackDay>>();
		for (Map.Entry<String, Map<Date, List<History>>> entry : mapCardByDay.entrySet()) {
			String key = entry.getKey();
			Map<Date, List<History>> historiesOnDate = entry.getValue();
			List<TrackDay> trackDays = new ArrayList<TrackDay>();
			for (Map.Entry<Date, List<History>> entry2 : historiesOnDate.entrySet()) {
				Date key2 = entry2.getKey();
				List<History> histories = entry2.getValue();
				TrackDay trackDay = calculateOnDay(key2, key, histories);
				trackDays.add(trackDay);
			}
			// System.out.print("trackDays.size=" + trackDays.size());
			mapOnAcc.put(key, trackDays);
		}
		return mapOnAcc;
	}

	public List<History> mergeReasons(List<History> histories, List<Reason> reasons) {
		return null;
	}

	private static Map<String, List<History>> groupHistoryByAccount(List<History> input) {
		Map<String, List<History>> map = new TreeMap<String, List<History>>();
		for (History history : input) {
			CardHolder cardHolder = history.getCardHolder();
			String username = cardHolder.getLastName().replaceAll("PFS-", "").toUpperCase();
			if (map.containsKey(username)) {
				List<History> list = map.get(username);
				list.add(history);
			} else {
				List<History> list = new ArrayList<History>();
				list.add(history);
				map.put(username, list);
			}
		}
		return map;
	}

	private static Map<String, List<Leave>> groupLeaveByAccount(List<Leave> leaves) {
		Map<String, List<Leave>> map = new TreeMap<String, List<Leave>>();
		for (Leave lv : leaves) {
			String username = lv.getUsername().toUpperCase();
			if (map.containsKey(username)) {
				List<Leave> list = map.get(username);
				list.add(lv);
			} else {
				List<Leave> list = new ArrayList<Leave>();
				list.add(lv);
				map.put(username, list);
			}
		}
		return map;
	}

	private static Map<String, Map<Date, List<History>>> groupHistoryOnAccountByDate(Map<String, List<History>> mapCard, int year, int month) {
		Map<String, Map<Date, List<History>>> map = new TreeMap<String, Map<Date, List<History>>>();
		for (Map.Entry<String, List<History>> entry : mapCard.entrySet()) {
			Calendar calendar = Calendar.getInstance();
			calendar.set(Calendar.YEAR, year);
			calendar.set(Calendar.MONTH, month);
			int maxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
			calendar.set(Calendar.DAY_OF_MONTH, 0);

			String key = entry.getKey();
			List<History> historiesOnCard = entry.getValue();
			// loop through month
			Map<Date, List<History>> mapOnDate = new TreeMap<Date, List<History>>();
			for (int co = 0; co < maxDay; co++) {
				calendar.add(Calendar.DATE, 1);
				Date date = calendar.getTime();

				List<History> listHistoriesAccDay = new ArrayList<History>();
				for (History history : historiesOnCard) {
					Date timeStamp = history.getTimeStamp();
					if (DateUtils.isSameDay(date, timeStamp)) {
						listHistoriesAccDay.add(history);
					} else if (timeStamp.compareTo(date) > 0) {
						break;
					}
				}
				mapOnDate.put(date, listHistoriesAccDay);
			}
			map.put(key, mapOnDate);
		}
		return map;
	}

	@SuppressWarnings("deprecation")
	private static TrackDay calculateOnDay(Date date, String username, List<History> tracksOffDay) {
		int i = 0;
		int cin = 0;
		int cout = 0;
		TrackDay trackDay = new TrackDay();
		trackDay.setUsername(username);
		trackDay.setDate(date);
		trackDay.setLevel(FoulLevel.DEFAULT);
		if (tracksOffDay == null || tracksOffDay.size() == 0) {
			trackDay.setFail(true);
			trackDay.setLevel(FoulLevel.FULL_DAY);
			// trackDay.setDate(date);
		}
		for (History history : tracksOffDay) {
			//System.out.println(history.getTimeStamp());
			// the first
			if (i == 0) {
				// trackDay.setId(history.getRecordID());				
				trackDay.setCheckIn(history.getTimeStamp());
				//trackDay.setCheckOut(history.getTimeStamp());
				cin = trackDay.getCheckIn().getHours() * 60 + trackDay.getCheckIn().getMinutes();
				if (cin > Constant.TIME_IN && cin < Constant.TIME_OUT) {
					trackDay.setFail(true);
					trackDay.setLevel(FoulLevel.CHECKIN_LATE);
				}else if(cin >= Constant.TIME_OUT){
					trackDay.setFail(true);
					trackDay.setLevel(FoulLevel.FULL_DAY);
				}else{					
					trackDay.setFail(true);
					trackDay.setLevel(FoulLevel.NO_CHECKOUT);
				}
			}
			// the last
			else if ( i == tracksOffDay.size() - 1) {
				//trackDay.setFail(false);
				//trackDay.setLevel(null);
				trackDay.setCheckOut(history.getTimeStamp());
				cout = trackDay.getCheckOut().getHours() * 60 + trackDay.getCheckOut().getMinutes();
				if (cout <= Constant.TIME_MIDDLE) {
					//trackDay.setCheckIn(history.getTimeStamp());
					trackDay.setFail(true);
					if(trackDay.getLevel().equals(FoulLevel.CHECKIN_LATE)){
						trackDay.setLevel(FoulLevel.LATE_NO_OUT);
					}
				} else if (cout > Constant.TIME_MIDDLE && cout < Constant.TIME_OUT) {
					trackDay.setFail(true);
					if(trackDay.getLevel().equals(FoulLevel.CHECKIN_LATE)){
						trackDay.setLevel(FoulLevel.CHECK_DOUBLE);
					}else{
						trackDay.setLevel(FoulLevel.CHECKOUT_SOON);
					}
				}else{//cout > = Constant.TIME_OUT
					if(trackDay.getLevel().equals(FoulLevel.NO_CHECKOUT)){
						trackDay.setFail(false);
						trackDay.setLevel(null);
					}
				}
				// Office time
				int ot = cout - cin;
				if (cin <= Constant.TIME_MIDDLE && cout > Constant.TIME_MIDDLE && cout <= Constant.TIME_RETURN) {
					ot = ot - (cout - Constant.TIME_MIDDLE);
				} else if (cin <= Constant.TIME_MIDDLE && cout > Constant.TIME_RETURN) {
					ot = ot - 60;
				}else if(cin > Constant.TIME_MIDDLE && cin <= Constant.TIME_RETURN){
					ot = ot - (Constant.TIME_RETURN - cin);
				}
				trackDay.setOfficeTime(ot);
				StringBuilder sb = new StringBuilder();
				sb.append(String.valueOf(ot / 60)).append("h ").append(String.valueOf(ot % 60)).append(" m");
				trackDay.setOfficeTimeStr(sb.toString());
				
				if(trackDay.getLevel() != null){
					if(trackDay.getLevel().equals(FoulLevel.NO_CHECKOUT) || trackDay.getLevel().equals(FoulLevel.LATE_NO_OUT)){
						trackDay.setCheckOut(null);
						trackDay.setOfficeTimeStr("");
					}
				}
			}
			i++;
		}
		return trackDay;
	}

	private static Map<String, List<TrackDay>> removeOutDates(Map<String, List<TrackDay>> mapTrack, List<Date> outDates) {
		Map<String, List<TrackDay>> newMap = new TreeMap<String, List<TrackDay>>();
		for (Map.Entry<String, List<TrackDay>> entry : mapTrack.entrySet()) {
			List<TrackDay> list = entry.getValue();
			List<TrackDay> newList = new ArrayList<TrackDay>();
			for (int i = 0; i < list.size(); i++) {
				boolean isContain = false;
				TrackDay trackDay = list.get(i);
				for (Date date : outDates) {
					if (DateUtils.isSameDay(trackDay.getDate(), date)) {
						// newList.add(trackDay);
						isContain = true;
						break;
					}
				}
				if (!isContain) {
					newList.add(trackDay);
				}
			}

			newMap.put(entry.getKey(), newList);
		}
		return newMap;
	}

	public static long getDateDiff(Date date2, Date date1) {
		return (date2.getTime() - date1.getTime()) / (24 * 60 * 60 * 1000);
	}
}
